import WebpackLicense from '@components/WebpackLicense';
import { Collapse, CollapsePanel } from '@components/Collapse';
import LoggerType from '../../types/logger.mdx';
import Columns from '@components/Columns';

<WebpackLicense from="https://webpack.docschina.org/api/logging/" />

# Logger

使用 Logger 输出消息是一种向用户展示信息的有效方式。

Rspack logger 可以在 [loaders](/guide/features/loader) 和 [plugins](/guide/features/plugin) 中使用。 生成的日志将作为 [Stats](/api/javascript-api/stats-json) 的一部分并且可以在 [rspack configuration](/config/) 中配置。

使用 Rspack 的 Logger 有如下收益：

- 可统一配置日志的展示级别。
- 日志可作为 `stats.json` 的一部分。
- Stats 的预设可控制日志的输出。
- 使用 Plugin 可以影响日志的捕获和展示级别。
- 档使用多个 Plugin 和 Loader 时，提供通用的日志方案。
- Rspack 的 CLI、UI 工具可能会选择不同的方式来展示日志。
- Rspack 核心也会输出日志，如一些时间统计数据

通过引入 Rspack 的 Logger API。我们希望能够统一 Plugin 和 Loader 输出日志的方式，以更方便地排查问题和提升开发体验。同时为非 CLI 的集成 Rspack 提供解决方案，如 dashboard 和其他 UI 工具。

:::warning 避免输出过多日志
**避免在输出过多日志!**

由于构建过程中使用多个 Plugin 和 Loader，一个 Loader 通常也会处理许多文件。尽量选择一个低等级的日志展示等级来保证日志的信息有效性。
:::

## 示例

### 在 Plugin 中使用

插件中有两种方式来获取 Logger：

1. [compilation.getLogger](/api/javascript-api/compilation#getlogger): 日志内容会被存储到 Stats 中，适合输出与一次 Compilation 关联的日志。
2. [compiler.getInfrastructureLogger](/api/javascript-api/compiler#getinfrastructurelogger): 日志内容不会被存储，适合输出不与 Compilation 关联的全局信息。

可以在插件中通过如下代码获得 Logger：

```js title=MyPlugin.js
const PLUGIN_NAME = 'my-plugin';
export class MyRspackPlugin {
  apply(compiler) {
    // 获得全局 Logger
    const logger = compiler.getInfrastructureLogger(PLUGIN_NAME);
    logger.log('log from compiler');

    compiler.hooks.compilation.tap(PLUGIN_NAME, compilation => {
      // 获得关联 Compilation 的 Logger
      const logger = compilation.getLogger(PLUGIN_NAME);
      logger.info('log from compilation');
    });
  }
}
```

### 在 Loader 中使用

可以在 Loader Context 上获取 Logger：

```js title=MyLoader.js
module.exports = function (source) {
  // access Logger from loader
  const logger = this.getLogger('my-loader');
  logger.info('hello Logger');
  return source;
};
```

## Logger API

### 基础 API

**类型：** `(...args: any[]): void;`

按照展示等级从高到低依次为：

- `error`: 用于输出错误信息。
- `warn`: 用于输出警告信息。
- `info`: 用于输出重要的信息，默认会被展示，确保这些信息需要被用户看到。
- `log`: 用于输出不重要的信息，默认不会被展示
- `debug`: 用于输出调试信息，需要通过特定的开关开启。

使用 `compilation.getLogger` 时，可通过 `stats.logging` 和 `stats.loggingDebug` 来控制日志的展示等级：

<Columns>
```js title="rspack.config.mjs"
export default {
  plugins: [{
    apply(compiler) {
      compiler.hooks.thisCompilation.tap("test plugin", compilation => {
        const logger = compilation.getLogger("TEST");
        logger.error("I am an error");
        logger.warn("I am a warning");
        logger.info("I am an information");
        logger.log("I am a log");
        logger.debug("I am a debug log");
      });
    }
  }],
  stats: {
    logging: "verbose",
    loggingDebug: true
  },
};
```

```js title=输出
asset main.js 264 bytes [emitted] (name: main)
runtime modules 124 bytes 2 modules
./index.js 15 bytes [built] [code generated]

DEBUG LOG from TEST
<e> I am an error
<w> I am a warning
<i> I am an information
    I am a log
    I am a debug log
```

</Columns>

当使用 `compiler.getInfrastructureLogger` 时，可通过 `infrastructureLogging.level` 和 `infrastructureLogging.debug` 来控制日志的展示等级：

<Columns>
```js title="rspack.config.mjs"
export default {
  plugins: [{
    apply(compiler) {
      compiler.hooks.thisCompilation.tap("test plugin", compilation => {
        const logger = compiler.getInfrastructureLogger("TEST");
        logger.error("I am an error");
        logger.warn("I am a warning");
        logger.info("I am an information");
        logger.log("I am a log");
        logger.debug("I am a debug log");
      });
    }
  }],
  infrastructureLogging: {
    level:  "verbose",
    debug: true
  },
};
```

```js title=输出
<e> [TEST] I am an error
<w> [TEST] I am a warning
<i> [TEST] I am an information
    [TEST] I am a log
    [TEST] I am a debug log
Rspack compiled successfully in 49 ms
```

</Columns>

### assert

当断言失败时展示错误信息。

- **等级：** `error`
- **类型：**: `assert(assertion: any, ...args: any[]): void;`

<Columns>
```js
logger.assert(false, "I am an assert error");
logger.assert(true, "Never displayed");
```

```js title=输出
LOG from TEST
<e> I am an assert error
```

</Columns>

### status

展示处理状态信息，默认会使用 `console.status`，如果不存在则降级到 `console.info`。

- **等级：** `info`
- **类型：** `status(...args: any[]): void`

<Columns>
```js
logger.status("status info");
```

```js title=Output
[TEST] status info
```

</Columns>

### trace

展示当前堆栈，仅当使用 Compilation Logger 且开启 `stats.loggingTrace` 可用。

- **等级：** `debug`
- **类型：** `trace(): void`

<Columns>
```js
logger.trace();
```

```js title=Output
DEBUG LOG from TEST
    Trace
|     at Object.fn
|     at SyncHook.callAsyncStageRange
```

</Columns>

### clear

清除所有之前已打印的所有日志，等同于 `console.clear()`。

- **等级：** `log`
- **类型：** `clear(): void;`

<Columns>
```js
logger.debug("not displayed");
logger.clear();
logger.debug("will displayed");
```

```js title=Output
[TEST] will displayed
```

</Columns>

### 分组 API

包括如下方法：

- `group(...args: any[]): void`: 创建一个日志分组，分组信息以 `logger.log` 展示。
- `groupEnd(...args: any[]): void`: 结束一个日志分组。
- `groupCollapsed(...args: any[]): void`: 将日志进行分组。默认显示为折叠 `logger.log` 日志，当日志记录级别设置为 'verbose' 或 'debug' 时，显示展开的日志。

<Columns>
```js
logger.group("Group");
logger.info("Info");
logger.log("Log");
logger.debug("Debug");
logger.groupCollapsed("Collapsed group");
logger.log("Log inside collapsed group");
logger.group("Inner group");
logger.log("Inner inner message");
logger.groupEnd();
logger.groupEnd();
logger.log("Log");
logger.groupEnd();
logger.log("End");
```

```js title=Output
<-> [TEST] Group
  <i> [TEST] Info
      [TEST] Log
      [TEST] Debug
  <-> [TEST] Collapsed group
        [TEST] Log inside collapsed group
    <-> [TEST] Inner group
          [TEST] Inner inner message
      [TEST] Log
    [TEST] End
```

</Columns>

### 时间 API

包括如下方法：

- `time(label: any): void`: 启动一个计时器。
- `timeLog(label: any): void`: 记录时间差但不关闭计时器。
- `timeEnd(label: any): void`: 记录时间差并关闭计时器。
- `timeAggregate(label: any): void`: 叠加记录时间差。
- `timeAggregateEnd(label: any): void`: 结束叠加记录时间差，并获得时间差总和。

<Columns>
```js
const wait = time => new Promise(resolve => setTimeout(resolve, time))
logger.time("normal");
await wait(100);
logger.timeLog("normal");
await wait(100);
logger.timeEnd("normal");

for (let i = 10;i--;) {
logger.time("aggregate")
await wait(i \* 10);
logger.timeAggregate("aggregate")
}
logger.timeAggregateEnd("aggregate")

````

```js title=Output
<t> [TEST] normal: 101.091167 ms
<t> [TEST] normal: 202.565 ms
<t> [TEST] aggregate: 460.416124 ms
````

</Columns>

### Profile API

包括如下方法：

- `profile(label: any): void`: 开启 Profile 捕获，如果支持会直接代理到 `console.profile`。
- `profileEnd(label: any): void`: 结束 Profile 捕获，如果支持会直接代理到 `console.profileEnd`。

### 子 Logger

可以通过 `logger.getChildLogger()` 来创建子 Logger，其方法完全一致。

<Columns>
```js
const logger = compiler.getInfrastructureLogger("TEST");
logger.info("logger info");
const childLogger = logger.getChildLogger("CHILD");
childLogger.info("child logger info");
```

```js title=Output
<i> [TEST] logger info
<i> [TEST/CHILD] child logger info
```

</Columns>
